package org.inasnet.user;

import java.io.*;
import java.net.*;

import org.inasnet.BytesPack;
import org.inasnet.Root;
import org.inasnet.audio.AudioManager;
import org.inasnet.audio.SessionSoundBuffer;
import org.inasnet.frame.TalkFrame;
import org.inasnet.user.Pack;

public class Session
{
	public enum TalkStatus
	{
		NONE,
		REQUEST,
		ONE_SIDE_DECLINED,
		TALK,
		PAUSED,
		ONE_SIDE_CLOSED,
		CLOSED,
	}
	
	class SessionCancelled extends Exception
	{
	}
	
	/**
	 * Calls, when somebody calls Client user
	 * @param socket - accepted socket
	 */
	Session( Socket socket ) throws SessionCancelled
	{
		mSocket = socket;
		
		try 
		{
			mInputStream  = new DataInputStream( mSocket.getInputStream() );
			mOutputStream = new DataOutputStream( mSocket.getOutputStream() );
		} 
		catch( Exception e ) 
		{
			e.printStackTrace();
			throw new SessionCancelled();
		}
		
		requestStatus();
		
		initDialog();
	}
	
	Session( User user ) throws SessionCancelled
	{		
		mTalkWith = user;
		
		try 
		{
			mSocket = new Socket( user.getAddress(), ConnectionSettings.mUserServerPort );
			
			mInputStream  = new DataInputStream( mSocket.getInputStream() );
			mOutputStream = new DataOutputStream( mSocket.getOutputStream() );
		} 
		catch( Exception e ) 
		{
			e.printStackTrace();
			throw new SessionCancelled();
		}
		
		requestStatus();

		initDialog();
	}
	
	void requestStatus()
	{
		// request status
		writeOutput( Pack.MessageType.REQUEST_STATUS, null );
	}
	
	private void initDialog()
	{
		AudioManager.connect( this );
		
		mTalkFrame = new TalkFrame( this );
		mTalkFrame.setVisible( true );

		mListeners = new SessionListener[ Pack.MessageType.values().length ];
		
		if( ! mListenersRegistered )
		{
			mListenersRegistered = true;
			
			new LoginListener();
			new MessagesListener();
		}
	}
	
	/**
	 * Create and send new pack
	 * @param type - Pack.MessageType
	 * @param messageBytes - bytes array
	 * @param messageBytesLength - send bytes from 0 till messageBytesLength
	 */
	void writeOutput( int type, byte [] messageBytes, int messageBytesLength )
	{
		if( mSocket == null )
			return;
		
		try
		{
			new PackOut( type, messageBytes, messageBytesLength, mOutputStream );
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}
	}
	
	void writeOutput( Pack.MessageType type, byte [] messageBytes )
	{
		writeOutput( type.ordinal(), messageBytes, messageBytes != null ? messageBytes.length : 0 );
	}
	
	void writeOutput( int type, byte [] messageBytes )
	{
		writeOutput( type, messageBytes, messageBytes != null ? messageBytes.length : 0 );
	}
	
	void writeOutput( Pack.MessageType type, byte [] audioBytes, int numBytesRead ) 
	{
		writeOutput( type.ordinal(), audioBytes, numBytesRead );
	}
	
	/**
	 * Read input data. TODO: Synchronize here
	 */
	public void doSession() 
	{
		if( mSocket == null )
			return;

		try 
		{
			if( mInputStream.available() != 0 )
			{
				PackIn in = new PackIn( mInputStream );
				
				int type = in.getType();
				
				if( mListeners[ type ] != null )
					mListeners[ type ].sendData( in, this );
				else
					throw new Exception( "Listener doesn't set for type `" + type + "`." );
			}
			
			if( mRequestStatusTicks > 5000000 )
			{
				//requestStatus();
				
				mRequestStatusTicks = 0;
			}
				
			//mRequestStatusTicks++;
		} 
		catch( Exception e ) 
		{
			e.printStackTrace();
		}
	}
	
	static void setListenerForType( int type, SessionListener listener ) throws Exception
	{
		if( mListeners[ type ] == null )
		{		
			mListeners[ type ] = listener;
		}
		else
			throw new Exception( "Session listener for type " + type + " is already set!" );
	}
	
	/**
	 * Session listener
	 */
	public abstract class SessionListener 
	{
		SessionListener()
		{
			mActivated = false;
			
			mPack = PackIn.createEmptyPack();
			
			checkData();
			
			mPack = null;
			
			mActivated = true;
		}
		
		abstract void checkData();
		
		private final void sendData( PackIn in, Session session )
		{
			mPack = in;
			mSession = session;
			
			checkData();
		}
		
		/**
		 * 
		 * @param messageType Pack.MessageType
		 * @return
		 */
		final boolean isType( int messageType )
		{
			if( ! mActivated )
				addType( messageType );
			
			return mPack.getType() == messageType;
		}
		
		final boolean isType( Pack.MessageType messageType )
		{
			return isType( messageType.ordinal() );
		}
		
		final byte [] getMessageBytes()
		{
			return mPack.getMessageBytes();
		}
		
		final int getMessageSize()
		{
			return mPack.getMessageSize();
		}
		
		final void sendMessage( Pack.MessageType messageType, byte [] messageBytes )
		{
			mSession.writeOutput( messageType.ordinal(), messageBytes );
		}
		
		final void sendMessage( Pack.MessageType messageType )
		{
			mSession.writeOutput( messageType.ordinal(), null );
		}
		
		final String getMessageString()
		{
			return new String( getMessageBytes(), 0, getMessageSize() ); 
		}
		
		final int getMessageInt() 
		{
			return Integer.parseInt( getMessageString() );
		}
		
		final Session getSession()
		{
			return mSession;
		}

		private final void addType( int messageType ) 
		{
			try 
			{
				setListenerForType( messageType, this );
			} 
			catch( Exception e ) 
			{
				e.printStackTrace();
			}
		}
		
		private boolean mActivated;
		
		private PackIn mPack;
		
		private Session mSession;	
	}

	/**
	 * Listener for login messages type.
	 * @author APtakhin
	 *
	 */
	class LoginListener extends SessionListener
	{
		void checkData() 
		{
			if( isType( Pack.MessageType.REQUEST_STATUS ) )
			{
				sendMessage( Pack.MessageType.ACCEPT_REQUEST_STATUS, 
							 ( "" + Root.getUser().getStatus().ordinal() ).getBytes() );
			}
			
			if( isType( Pack.MessageType.ACCEPT_REQUEST_STATUS ) )
			{
				getSession().setStatusToTalkUser( getMessageInt() );
			}
		}
	}
	
	/**
	 * Listener for transferring messages
	 * @author APtakhin
	 *
	 */
	class MessagesListener extends SessionListener
	{
		void checkData() 
		{
			if( isType( Pack.MessageType.DIALOG_VOICE_MESSAGE ) )
			{
				mSoundBuffer.getInputSound().addAudioBytes( getMessageBytes() );
				//requestStatus();
			}
		}
	}

	public User getUserTalkWith() 
	{
		return mTalkWith;
	}

	public void setStatusToTalkUser( int status ) 
	{
		mTalkWith.setStatus( status ); 
		
		mTalkFrame.updateStatus( Session.TalkStatus.values()[ status ] );
	}
	
	///*
	public void sendAudioBytes( byte [] audioBytes ) 
	{
		writeOutput( Pack.MessageType.DIALOG_VOICE_MESSAGE, audioBytes );
	}
	
	public void sendAudioBytes( byte [] audioBytes, int numBytesRead ) 
	{
		writeOutput( Pack.MessageType.DIALOG_VOICE_MESSAGE, audioBytes, numBytesRead );
	}
	//*/

	public SessionSoundBuffer getSoundBuffer() 
	{
		return mSoundBuffer;
	}
	
	public void talk() 
	{
		mTalkFrame.dragOnTop();
	}
	
	boolean mListenersRegistered;
	
	TalkStatus mTalkStatus = TalkStatus.NONE;
	
	Socket mSocket;
	
	DataInputStream mInputStream;
	
	DataOutputStream mOutputStream;
	
	TalkFrame mTalkFrame;
	
	static SessionListener [] mListeners;
	
	User mTalkWith;
	
	SessionSoundBuffer mSoundBuffer = new SessionSoundBuffer( this );

	private int mRequestStatusTicks;
}
